using System;

namespace MetaWorld
{
	/// <summary>
	/// The oscillator class provides a simple facility for producing
	/// fixed-frequency waves. It can produce linear triangular waves 
	/// and sinusoids with a given period or frequency. Use the static 
	/// functions WithPeriod and WithFrequency to create new instances.
	/// </summary>
	public class Oscillator
	{
		/// <summary>
		/// Oscillator's frequency in cycles per second
		/// </summary>
		public double Frequency;

		/// <summary>
		/// Oscillator's phase shift in seconds
		/// </summary>
		public double Shift;

		/// <summary>
		/// Internal: timestamp for when the oscillator was initialized
		/// (used to calculate phase shift).
		/// </summary>
		protected DateTime StartTime;

		/// <summary>
		/// Internal: creates an oscillator with given frequency (in Hz)
		/// and phase shift in seconds.
		/// </summary>
		/// <param name="frequency">Oscillation frequency in Hertz</param>
		/// <param name="shift">Oscillation phase shift in seconds</param>
		protected Oscillator (double frequency, double shift)
		{
			this.Frequency = frequency;
			this.Shift = shift;
			ResetTimer ();
		}

		/// <summary>
		/// Returns a new oscillator with given frequency (in Hertz).
		/// </summary>
		/// <param name="Hz"></param>
		/// <returns></returns>
		public static Oscillator WithFrequency (double Hz)
		{
			return WithFrequency (Hz, 0.0);
		}

		/// <summary>
		/// Returns a new oscillator with given frequency (in Hertz)
		/// and phase shift (in seconds).
		/// </summary>
		/// <param name="Hz"></param>
		/// <param name="shift"></param>
		/// <returns></returns>
		public static Oscillator WithFrequency (double Hz, double shift)
		{
			return new Oscillator (Hz, shift);
		}


		/// <summary>
		/// Returns a new oscillator with given period (in seconds).
		/// </summary>
		/// <param name="period"></param>
		/// <returns></returns>
		public static Oscillator WithPeriod (double period)
		{
			return WithPeriod (period, 0.0);
		}

		/// <summary>
		/// Returns a new oscillator with given period (in seconds)
		/// and given phase shift (in seconds).
		/// </summary>
		/// <param name="period"></param>
		/// <param name="shift"></param>
		/// <returns></returns>
		public static Oscillator WithPeriod (double period, double shift)
		{
			return new Oscillator (1 / period, shift);
		}

		/// <summary>
		/// Resets the oscillator, so it starts running from the beginning.
		/// </summary>
		public void ResetTimer ()
		{
			StartTime = DateTime.Now;
			double floor = Math.Floor (Shift * 10000000);
			long ticks = Convert.ToInt64 (floor);
			StartTime += new TimeSpan (ticks);
		}

		/// <summary>
		/// Returns the value as a linear reading, traversing the range [0, 1] 
		/// through the oscillation period.
		/// </summary>
		/// <returns></returns>
		public double Linear
		{
			get 
			{
				TimeSpan delta = (DateTime.Now - StartTime);
				double seconds = delta.TotalSeconds * Frequency;
				return seconds - Math.Floor (seconds);
			}
		}

		/// <summary>
		/// Simple linear reading, traverses the range [0, pi] 
		/// through the oscillation period.
		/// </summary>
		/// <returns></returns>
		public double HalfRadial
		{
			get 
			{
				return Linear * Math.PI;
			}
		}

		/// <summary>
		/// Simple linear reading, traverses the range [0, 2pi] 
		/// through the oscillation period.
		/// </summary>
		/// <returns></returns>
		public double Radial
		{
			get 
			{
				return Linear * Math.PI * 2;
			}
		}


		/// <summary>
		/// Sinusoidal reading, traverses one period of a sine wave
		/// (within the range [-1, 1]) through the oscillation period.
		/// </summary>
		public double Sine
		{
			get 
			{
				return Math.Sin (Radial);
			}
		}

		/// <summary>
		/// Sinusoid-squared reading, traverses a period of one sin^2
		/// wave (two peaks, in [0, 1]) through the oscillation period.
		/// </summary>
		public double SineSquare 
		{
			get
			{
				return Sine * Sine;
			}
		}

		/// <summary>
		/// Cosine reading, traverses one period of a cosine wave 
		/// (within the range [-1, 1]) through the oscillation period.
		/// </summary>
		public double Cosine 
		{
			get 
			{ 
				return Math.Cos (Radial);
			}
		}

	}
}
